/*
 * Filename H5ReportService.java 2022年05月17日
 * Copyright © Ehome Co.Ltd. All Rgiths Reserved.
 * @author wuyingchao
 */
package com.ehome.poct.core.service.report;

import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.convert.Convert;
import cn.hutool.core.util.StrUtil;
import cn.hutool.extra.qrcode.QrCodeUtil;
import cn.hutool.extra.qrcode.QrConfig;
import cn.hutool.json.JSONUtil;
import com.ehome.oss.config.OssClient;
import com.ehome.poct.base.api.enums.StripTypeEnums;
import com.ehome.poct.core.api.dto.check.CheckItemIndicatorDTO;
import com.ehome.poct.core.api.dto.check.CheckProjectDTO;
import com.ehome.poct.core.api.dto.check.CheckReportDTO;
import com.ehome.poct.core.api.dto.check.gold.TestConfPoctStripTypeDTO;
import com.ehome.poct.core.api.dto.check.gold.TestReportPoctUniDTO;
import com.ehome.poct.core.api.dto.checkdata.CheckItemResultDTO;
import com.ehome.poct.core.api.dto.common.CommonUtil;
import com.ehome.poct.core.api.dto.order.TestOrderDTO;
import com.ehome.poct.core.api.virtual.H5ReportService;
import com.ehome.poct.core.api.virtual.TestReportDiseaseConfigService;
import com.ehome.poct.core.api.virtual.WxService;
import com.ehome.poct.core.api.vo.CheckChartDataVO;
import com.ehome.poct.core.api.vo.CheckResultVO;
import com.ehome.poct.core.api.vo.ResultImgVO;
import com.ehome.poct.core.api.vo.report.H5ReportDetailVO;
import com.ehome.poct.core.api.vo.report.ReportDataVO;
import com.ehome.poct.core.repository.*;
import com.ehome.poct.core.repository.checkdata.BldCheckDataRepository;
import com.ehome.poct.core.repository.order.TestOrderRepository;
import com.ehome.poct.core.repository.report.TermPatientRepository;
import com.ehome.poct.core.service.analysis.RbcAnalysis;
import com.ehome.poct.core.service.analysis.WbcAnalysis;
import com.ehome.poct.core.service.mapping.H5ReportMapping;
import lombok.AllArgsConstructor;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.InputStream;
import java.math.BigDecimal;
import java.time.LocalDateTime;
import java.time.ZoneOffset;
import java.util.Comparator;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.concurrent.CompletableFuture;
import java.util.function.Function;
import java.util.stream.Collectors;

import static com.ehome.poct.core.api.vo.CheckChartDataVO.Point;

/**
 * H5报告服务
 *
 * @author wyc
 * @since 2022/05/17 16:39
 **/

@Service
@AllArgsConstructor
public class H5ReportServiceImpl implements H5ReportService {
    private final BldReportRepository bldReportRepository;
    private final CheckItemIndicatorRepository indicatorRepository;
    private final BldCheckDataRepository bldCheckDataRepository;
    private final CheckProjectRepository bldProjectRepository;
    private final TermPatientRepository patientRepository;
    private final TestOrderRepository orderRepository;
    private final TestReportPoctUniRepository jbyUniReportRepository;
    private final TestConfPoctStripTypeRepository jbyProjectRepository;
    private final CheckProjectRepository checkProjectRepository;
    private final TestReportDiseaseConfigService diseaseConfigService;
    private final WbcAnalysis wbcAnalysis;
    private final RbcAnalysis rbcAnalysis;
    private final WxService wxService;
    private final H5ReportMapping reportMapping;
    @Autowired
    private  OssClient ossClient;


    @Override
    public H5ReportDetailVO getBldSigReportByOrderNo(String orderNo) {
        final var report = bldReportRepository.selectOne(new CheckReportDTO().setOrderNo(orderNo));
        // 微信推送链接
        final var pushLinkFuture = CompletableFuture.supplyAsync(() ->
                wxService.generateNotifyUrl(report.getBloodCheckReportId(), 2)
        );
        final var data = bldCheckDataRepository.getById(report.getBloodCheckDataId());
        // 检测信息
        final var baseFuture = CompletableFuture.supplyAsync(() -> {
            final var project = bldProjectRepository.selectByCode(report.getProjectCode());
            final var order = orderRepository.selectOne(new TestOrderDTO().setTestOrderNo(report.getOrderNo()));
            return reportMapping.to(data, project, order);
        });

        // 患者信息
        final var patientVO = reportMapping.to(patientRepository.getById(report.getPatientId()));

        // 血常规检测数据
        final var kpiMap = indicatorRepository.findByType(patientVO.getPatientType())
                .stream().collect(Collectors.toMap(CheckItemIndicatorDTO::getSimpleName,  Function.identity()));
        final var bldResults = parseCheckResult(data.getCheckResult(), kpiMap);
        final var morphologyResults = parseMorphologyCheckResult(data.getCheckResultMorphology());

        final var wbcAnalysisFuture = CompletableFuture.supplyAsync(() ->
                wbcAnalysis.analysis(bldResults, processSigCode(report.getWbcResultCode()), report.getClinicId())
        );
        final var rbcAnalysisFuture = CompletableFuture.supplyAsync(() ->
                rbcAnalysis.analysis(bldResults, morphologyResults, report.getRdwResultCode(), report.getClinicId())
        );

        // 组合报告数据
        final var chart = JSONUtil.toList(data.getCheckResultChatData(), CheckChartDataVO.class);
        chart.stream().filter(c -> CollUtil.isNotEmpty(c.getData())).forEach(c -> {
            final var max = c.getData().stream().max(Comparator.comparing(Point::getY))
                    .map(Point::getY).orElse(BigDecimal.ZERO);
            c.setMaxValue(max);
        });
        final var wbcAnalysis = wbcAnalysisFuture.join();
        final var rbcAnalysis = rbcAnalysisFuture.join();
        final var reportData = new ReportDataVO(chart, JSONUtil.toList(data.getCheckResultImgs(), ResultImgVO.class),
                bldResults, rbcAnalysis.getMorphology(),null, wbcAnalysis.getAbnormal(), rbcAnalysis.getAbnormal());

        return reportMapping.to(patientVO, baseFuture.join(), reportData, wbcAnalysis.getAnalysisContent(), rbcAnalysis.getAnalysisContent(), null, pushLinkFuture.join());
    }

    @Override
    public H5ReportDetailVO getBldReportByOrderNo(String orderNo) {
        // 三联测查询  如果不是三联测走这段逻辑会报错 http://jira.ehome.com/browse/HARDSOFT-1590
        TestReportPoctUniDTO reportPoctUniDTO = new TestReportPoctUniDTO().setPayOrderNo(orderNo)
                .setStripTypeCodeA(StripTypeEnums.CRP.getStripTypeCode())
                .setStripTypeCodeB(StripTypeEnums.SAA.getStripTypeCode());
        final var jbyReport = jbyUniReportRepository.selectOne(reportPoctUniDTO);
        var h5report = Optional.ofNullable(jbyReport).map(this::jbyReportToOlReport).orElseGet(() -> getBldSigReportByOrderNo(orderNo));
        String url =createQrCode(h5report.getPushLink(), orderNo+LocalDateTime.now().toEpochSecond(ZoneOffset.of("+8"))+".jpg");
        h5report.setQrCodeUrl(url);
        return h5report;
    }

    @Override
    public H5ReportDetailVO getOlReportByJbyId(Long id) {
        final var jbyReport = jbyUniReportRepository.getById(id);
        var h5report = jbyReportToOlReport(jbyReport);
        String url =createQrCode(h5report.getPushLink(), id+LocalDateTime.now().toEpochSecond(ZoneOffset.of("+8"))+".jpg");
        h5report.setQrCodeUrl(url);
        return h5report;
    }

    @Override
    public void updateReportResultCode(String orderNo) {
        final var bldReport = bldReportRepository.selectOne(new CheckReportDTO().setOrderNo(orderNo));
        final var data = bldCheckDataRepository.getById(bldReport.getBloodCheckDataId());
        final var project = checkProjectRepository.selectOne(new CheckProjectDTO().setCheckProjectCode(data.getCheckType()));
        final var patient = reportMapping.to(patientRepository.getById(bldReport.getPatientId()));
        final var projectCode = project.getCheckProjectCode() == 121 ? 100 : project.getCheckProjectCode();

        bldReportRepository.updateResultCode(bldReport,
                JSONUtil.toList(data.getCheckResult(), CheckItemResultDTO.class), patient.getPatientType(), projectCode);
    }

    private H5ReportDetailVO jbyReportToOlReport(TestReportPoctUniDTO jbyReport) {
        final var crp = Convert.toBigDecimal(jbyReport.getAnalysisResultA());
        final var saa = Convert.toBigDecimal(jbyReport.getAnalysisResultB());
        final var bldReport = bldReportRepository.selectOne(new CheckReportDTO().setOrderNo(jbyReport.getPayOrderNo()));
        // 推送链接
        final var pushLinkFuture = CompletableFuture.supplyAsync(() ->
                wxService.generateNotifyUrl(jbyReport.getTestReportUniPoctId(), 4)
        );
        // 病症关联
        final var diseaseCategoryFuture = CompletableFuture.supplyAsync(() ->
                diseaseConfigService.getWithGroupBy(jbyReport.getReportCode(), crp, saa, 5)
        );
        // 患者信息
        final var patientFuture = CompletableFuture.supplyAsync(() ->
                reportMapping.to(patientRepository.getById(jbyReport.getPatientId()))
        );
        // 金标仪检测结果
        final var jbyResultFuture = CompletableFuture.supplyAsync(() -> {
            final var crpStrip = jbyProjectRepository.selectOne(new TestConfPoctStripTypeDTO().setTypeCode(jbyReport.getStripTypeCodeA()));
            final var saaStrip = jbyProjectRepository.selectOne(new TestConfPoctStripTypeDTO().setTypeCode(jbyReport.getStripTypeCodeB()));
            String[] crpSuperAndNormal=getCrpSaaSuperNoHtml(getCrpSuperSeparate(crp,crpStrip));
            return List.of(parseCrpResult(crp,crpStrip,"C反应蛋白",crpSuperAndNormal[0]).setType("CRP检测"),parseCrpResult(crp,crpStrip,"超敏C反应蛋白",crpSuperAndNormal[1]).setType("hs-CRP检测"), parseJbyResult(saa, saaStrip).setType("SAA检测"));
        });

        final var data = bldCheckDataRepository.getById(bldReport.getBloodCheckDataId());
        // 基本检测信息
        final var baseFuture = CompletableFuture.supplyAsync(() -> {
            final var project = bldProjectRepository.selectByCode(bldReport.getProjectCode());
            final var order = orderRepository.selectOne(new TestOrderDTO().setTestOrderNo(bldReport.getOrderNo()));
            return reportMapping.to(data, project, order);
        });

        // 血常规检测数据
        final var patient = patientFuture.join();
        final var kpiMap = indicatorRepository.findByType(patient.getPatientType())
                .stream().collect(Collectors.toMap(CheckItemIndicatorDTO::getSimpleName,  Function.identity()));
        // 血常规检测数据
        final var bldResults = parseCheckResult(data.getCheckResult(), kpiMap);
        final var morphologyResults = parseMorphologyCheckResult(data.getCheckResultMorphology());
        // 金标仪检测数据
        final var jbyResults = jbyResultFuture.join();
        final  var jbyResultCode =jbyResults.stream().collect(Collectors.toMap(CheckResultVO::getOrder,CheckResultVO::getCode, (o1, o2) -> o1))
                .values().stream().collect(Collectors.joining());


        final var wbcAnalysisFuture = CompletableFuture.supplyAsync(() ->
                wbcAnalysis.unionAnalysis(bldResults, jbyResults, crp, saa, processUniCode(bldReport.getWbcResultCode(), jbyResultCode), bldReport.getClinicId())
        );
        final var rbcAnalysisFuture = CompletableFuture.supplyAsync(() ->
                rbcAnalysis.analysis(bldResults, morphologyResults, bldReport.getRdwResultCode(), bldReport.getClinicId())
        );

        // 组合报告数据
        final var chart = JSONUtil.toList(data.getCheckResultChatData(), CheckChartDataVO.class);
        chart.stream().filter(c -> CollUtil.isNotEmpty(c.getData())).forEach( c -> {
            final var max = c.getData().stream().max(Comparator.comparing(Point::getY))
                    .map(Point::getY).orElse(BigDecimal.ZERO);
            c.setMaxValue(max);
        });

        final var wbcAnalysis = wbcAnalysisFuture.join();
        final var rbcAnalysis = rbcAnalysisFuture.join();
        final var reportData = new ReportDataVO(chart, JSONUtil.toList(data.getCheckResultImgs(), ResultImgVO.class),
                bldResults, rbcAnalysis.getMorphology(), jbyResults, wbcAnalysis.getAbnormal(), rbcAnalysis.getAbnormal());
        return reportMapping.to(patient, baseFuture.join(), reportData, wbcAnalysis.getAnalysisContent(), rbcAnalysis.getAnalysisContent(), diseaseCategoryFuture.join(), pushLinkFuture.join());
    }


//    private Map<String, List<CheckResultVO>> parseCheckResult(String result, Map<String, CheckItemIndicatorDTO> map) {
//        final var results = JSONUtil.toList(result, CheckResultVO.class);
//        final var rbcRateList = List.of("LARGE_RBC_RATE", "SMALL_RBC_RATE");
//        return results.stream().collect(Collectors.groupingBy(r -> {
//            final var algoStatus = r.getStatus();
//            if (rbcRateList.contains(r.getType())) {
//                return algoStatus == 2 ? "morphology" : "morphologyEnable";
//            }
//            final var value = Convert.toBigDecimal(r.getValue(), BigDecimal.ZERO);
//            final var kpi = map.get(r.getType());
//            reportMapping.to(r, kpi);
//            if (value.compareTo(kpi.getMaxValue()) > 0) {
//                r.setValue(value + " ↑").setStatus(2);
//            } else if (value.compareTo(kpi.getMinValue()) < 0) {
//                r.setValue(value + " ↓").setStatus(2);
//            }
//            // 算法标记的异常数值，需添加 * 号
//            if (algoStatus == 1) {
//                r.setValue(r.getValue() + " *");
//            }
//            return "base";
//        }));
//    }

    private List<CheckResultVO> parseCheckResult(String result, Map<String, CheckItemIndicatorDTO> map) {
        final var results = JSONUtil.toList(result, CheckResultVO.class);
        results.forEach(r -> {
            final var algoStatus = r.getStatus();
            final var value = Convert.toBigDecimal(r.getValue(), BigDecimal.ZERO);
            final var kpi = map.get(r.getType());
            reportMapping.to(r, kpi);
            if (value.compareTo(kpi.getMaxValue()) > 0) {
                r.setValue(value + " ↑").setStatus(2);
            } else if (value.compareTo(kpi.getMinValue()) < 0) {
                r.setValue(value + " ↓").setStatus(2);
            }
            // 算法标记的异常数值，需添加 * 号
            if (algoStatus == 1) {
                r.setValue(r.getValue() + " *");
            }
        });
        return results;
    }

    private List<CheckResultVO> parseMorphologyCheckResult(String result) {
        if (StrUtil.isBlank(result)) {
            return null;
        }
        final var results = JSONUtil.toList(result, CheckResultVO.class);
        return results.stream().filter(it -> it.getStatus() == 2).collect(Collectors.toList());
    }


    public CheckResultVO parseJbyResult(BigDecimal value, TestConfPoctStripTypeDTO strip) {
        final var result = reportMapping.to(strip);
        final var rrsArray = strip.getReference().split("~");
        final var minRRS = new BigDecimal(rrsArray[0]);
        final var maxRRS = new BigDecimal(rrsArray[1]);

        if (value.compareTo(minRRS) < 0) {
            result.setStatus(2).setCode("2");
        } else if (value.compareTo(maxRRS) > 0) {
            result.setStatus(2).setCode("4");
        }

        return result.setValue(getCrpSuperSeparate(value,strip));
    }

    public String getCrpSuperSeparate(BigDecimal value,TestConfPoctStripTypeDTO strip){
        final var rrsArray = strip.getReference().split("~");
        final var minRRS = new BigDecimal(rrsArray[0]);
        final var maxRRS = new BigDecimal(rrsArray[1]);
        var tag = "";
        if (value.compareTo(minRRS) < 0) {
            tag = " ↓";
        }
        if (value.compareTo(maxRRS) >= 0) {
            tag = " ↑";
        }
        if (strip.getTypeCode().equals(4)) {
            if (value.compareTo(strip.getMax()) >= 0) {
                return "常规:≥" + strip.getMax() + "超敏:≥" + maxRRS;
            } else if (value.compareTo(maxRRS) > 0) {
                return "常规:" + value + tag + "超敏:≥" + maxRRS;
            } else {
                return "常规:<" + maxRRS + "超敏:" + value;
            }
        }
        else {
            if (value.compareTo(strip.getMax()) >= 0) {
                return "≥" + strip.getMax()+tag ;
            } else if (value.compareTo(strip.getMin()) <= 0) {
                return "≤" + strip.getMin()+tag;
            }
        }
        return  value +tag;
    }

    public String[] getCrpSaaSuperNoHtml(String crpOrignValue){
        return CommonUtil.processCrpSaaSuperNoHtml(crpOrignValue);
    }

    public CheckResultVO parseCrpResult(BigDecimal value, TestConfPoctStripTypeDTO strip,String stripName,String valueStr) {
        final var result = reportMapping.to(strip);
        result.setTypeName(stripName);

        final var rrsArray = strip.getReference().split("~");
        final var minRRS = new BigDecimal(rrsArray[0]);
        final var maxRRS = new BigDecimal(rrsArray[1]);

        if (value.compareTo(minRRS) < 0) {
            result.setStatus(2).setCode("2");
        } else if (value.compareTo(maxRRS) > 0) {
            result.setStatus(2).setCode("4");
        }

        return result.setValue(valueStr.replace("常规:","").replace("超敏:",""));
    }

    private String processSigCode(String code) {
        if (code.startsWith("1")) {
            return "100000000000";
        } else if (code.startsWith("5")) {
            return "500000000000";
        }
        return StrUtil.replace(code, 3, 6, '0');
    }

    private String processUniCode(String bldCode, String jbyCode) {
        if (bldCode.startsWith("1")) {
            return "00100000000000";
        } else if (bldCode.startsWith("5")) {
            return "00500000000000";
        }
        return jbyCode + StrUtil.replace(bldCode, 5, 6, '0');
    }

    private  String createQrCode(String content,String picName){
        QrConfig qrConfig = new QrConfig(160,160);
        ByteArrayOutputStream qrstream  = new ByteArrayOutputStream();
        QrCodeUtil.generate(content,qrConfig,"png",qrstream);
        InputStream inputStream = new ByteArrayInputStream(qrstream.toByteArray());
        String originUrl = ossClient.upload(picName,inputStream);
        return "https://poct-media.ehomepoct.com/wxqrcode/"+picName;
    }
}